/**
 * @file session.cpp
 * @brief Session class methods implementation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * @author VestniK (Sergey N.Vidyuk) sir.vestnik@gmail.com
 * @date 12 Nov 2009
 */
#include "session.h"

#include <QtNetwork/QHostAddress>

#include "sessionservice.h"
#include "sessionfactory.h"
#include "playerservice.h"
#include "gameservice.h"
#include "game.h"
#include "serverplayer.h"
#include "logger.h"

using namespace mjn;

/**
 * Creates new session which uses given socket for communication.
 *
 * This session will be deleted by the event loop if the socket is closed.
 *
 * @note if socket is not in connected state this instance will be deleted
 * right after return to the event loop.
 */
Session::Session(SessionFactory *parent): QObject(parent) {
    mFactory = parent;
    mSocket = 0;

    mManager = new qrs::ServicesManager(this);
    mService = new qrs::SessionService(mManager);
    connect( mService,SIGNAL(init()),
             this,SLOT(onInit()) );
    connect( mService,SIGNAL(restore(QUuid)),
             this,SLOT(onRestore(const QUuid &)) );
    connect( mService,SIGNAL(close()),
             this,SLOT(onClose()) );

    mClientAddr = tr("no socket associated");
    connect( mManager,SIGNAL(error(qrs::ServicesManager*,qrs::Message::ErrorType,QString)),
             this,SLOT(onIncomingErrorMessage(qrs::ServicesManager*,qrs::Message::ErrorType,QString)) );
    connect( mManager,SIGNAL(clientError(qrs::ServicesManager*,qrs::Message::ErrorType,QString)),
             this,SLOT(onProtocolError(qrs::ServicesManager*,qrs::Message::ErrorType,QString)) );
}

/**
 * @brief Sets socket to be used for comunications.
 *
 * @note This object takes ownership on the socket given.
 */
void Session::setSocket(QTcpSocket *socket) {
    if ( socket->state() != QTcpSocket::ConnectedState ) {
        return;
    }
    mSocket = socket;
    mSocket->setParent(this);
    mClientAddr = mSocket->peerAddress().toString();

    mManager->addDevice(mSocket);

    connect( mSocket,SIGNAL(disconnected()),
             this,SLOT(onConnectionClosed()) );
    connect( mSocket,SIGNAL(error(QAbstractSocket::SocketError)),
             this,SLOT(onSocketError()) );
}

void Session::connectPlayer(ServerPlayer *player) {
    qrs::PlayerService *playerService = new qrs::PlayerService(mManager);
    playerService->autoconnect(player);
    connect( player,SIGNAL(updated()),
             mFactory,SLOT(notifySessions()) );
}

void Session::connectGame() {
    Game *game = mFactory->game();
    qrs::GameService *gameService = new qrs::GameService(mManager);
    gameService->autoconnect(game);
}

void Session::onInit() {
    if ( !mSid.isNull() ) {
        log()->info(Logger::Session,
                    tr("Request from '%1' to init session while session '%2' already started.")
                        .arg(mClientAddr)
                        .arg(mSid.toString()) );
        mService->sessionAlreadyStarted(mSid);
        return;
    }
    if ( mFactory->game()->noSlots() ) {
        log()->info(Logger::Session,
                    tr("Game has no free slots. Request from '%1' to init session rejected.")
                        .arg(mClientAddr) );
        mService->noSlots();
        if ( mSocket ) mSocket->disconnectFromHost();
        return;
    }
    mSid = QUuid::createUuid();
    ServerPlayer *player = mFactory->game()->addNewPlayer();

    connectPlayer(player);
    connectGame();

    SessionData data;
    data.player = player;
    data.session = QPointer<Session>(this);
    data.disconnected = false;
    mFactory->storeData(mSid,data);

    mService->sessionStarted(mSid);
    log()->info(Logger::Session,
                tr("Request from '%1' to init new session accepted. Session '%2' started.")
                    .arg(mClientAddr)
                    .arg(mSid.toString()) );
    mFactory->notifySessions();
    if (mFactory->game()->state() == Game::ReadyToStart) {
        mFactory->game()->start();
    }
}

void Session::onRestore(const QUuid &sid) {
    SessionData *data = mFactory->getData(sid);
    if ( data == 0 ) {
        log()->info(Logger::Session,
                    tr("Request from '%1' to recover not existing session '%2' rejected.")
                        .arg(mClientAddr)
                        .arg(mSid.toString()) );
        mService->noSuchSession();
        return;
    }
    if ( ! data->session.isNull() ) {
        log()->error(Logger::Session,
                     tr("Request from '%1' to stole session '%2' rejected.")
                        .arg(mClientAddr)
                        .arg(mSid.toString()) );
        mService->noSuchSession();
        return;
    }
    ServerPlayer *player = data->player;
    mSid = sid;
    data->disconnected = false;

    connectPlayer(player);
    connectGame();

    mService->sessionRestored();
    log()->info(Logger::Session,
                tr("Request from '%1' to recover session '%2' accepted.")
                    .arg(mClientAddr)
                    .arg(mSid.toString()) );
    mFactory->notifySessions();
}

void Session::onClose() {
    log()->info(Logger::Session,
                tr("Request from '%1' to close the session '%2'.")
                    .arg(mClientAddr)
                    .arg(mSid.toString()) );
    if (mSocket) mSocket->disconnectFromHost();
    mFactory->removeData(mSid);
    mSid = QUuid();
}

void Session::onSocketError() {
    log()->error(Logger::Network,
                 tr("Socket error (%1): '%2' from '%3'")
                     .arg(mSocket->error())
                     .arg(mSocket->errorString())
                     .arg(mClientAddr) );
}

void Session::onConnectionClosed() {
    if ( mSid.isNull() ) {
        log()->info(Logger::Network,
                    tr("Connection from '%1' closed.").arg(mClientAddr) );
    } else {
        log()->error(Logger::Network,
                     tr("Connection with '%1' lost.").arg(mClientAddr) );
        log()->error(Logger::Session,
                     tr("Session '%1' disconnected.").arg(mSid.toString()));
        mFactory->getData(mSid)->disconnected = true;
        mFactory->notifySessions();
    }
}

Session::~Session() {
}

void Session::onProtocolError(qrs::ServicesManager *manager,
                              qrs::Message::ErrorType errorCode,
                              const QString &errorString) {
    log()->error(Logger::Protocol,
                 tr("Client '%1' (session '%2') sent incorrect message. Protocol error (%3): '%4'")
                     .arg(mClientAddr)
                     .arg(mSid.toString())
                     .arg(errorCode)
                     .arg(errorString) );
}

void Session::onIncomingErrorMessage(qrs::ServicesManager *manager,
                                     qrs::Message::ErrorType errorCode,
                                     const QString &errorString) {
    log()->error(Logger::Protocol,
                 tr("Received error message from '%1' (session '%2'). Protocol error (%3): '%4'")
                     .arg(mClientAddr)
                     .arg(mSid.toString())
                     .arg(errorCode)
                     .arg(errorString) );
}

void Session::sendInfo(const QList<SessionInfo> &sessionsInfo)
{
    mService->updateInfo(sessionsInfo);
}
